# OTableau : WYSIWYG LaTeX code generator for OT tableaux 
# Copyright (C) 2006 Julien Eychenne
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
# MA  02110-1301, USA.

import sys, os, re, urllib, csv
from Tkinter import *
from tkSimpleDialog import askinteger
from tkMessageBox import *
from tkFileDialog import *
import rcd

from OTabSettings import *
import OTabTextData
from OTabTextWindow import TextWindow
from __version__ import *
import OTGrammar
import OTFile #from OTFile import *

from operator import gt
from util.lst import some, concat
from util.fnc import uncurry
from util.reflect import traced

class GUITableau(Frame):
    def __init__(self, parent = None, tableau = None):
        Frame.__init__(self, parent)
        self.master = parent
        if tableau:
            self.gmr = tableau
        else:
            self.gmr = OTGrammar.Tableau([['','',''],
                                          ['',0,0],
                                          ['',0,0]])
        self.gmr.initialize()
        self.gmr.isEvaled = False
        
        self.params = readParameters()
        self.params['version'] = version
        
        self.userShade = True
        self.includeRankings = True
        self.useTipa = True
        self.footnoteSize = False
        self.useRotate = False
        self.evaluateMappings = False
        
        self.setTableauFrame()
        
        self.displayTableau()
        self.setButtons()
        self.setMenu(parent)

    def setMenu(self, parent):
        self.menu = Menu(parent)
        parent.config(menu=self.menu)
        
        file = Menu(parent, tearoff=0)
        file.add_command(label='New',
                         command=self.onNew, 
                         accelerator='%s+N' % acc,
                         underline=0)
        file.add_command(label='Open...',
                         command=self.onOpen, 
                         accelerator='%s+O' % acc,
                         underline=0)
        file.add_separator()
        file.add_command(label='Save as...',
                         command=self.onSave, 
                         accelerator='%s+S' % acc,
                         underline=0)
        file.add_separator()
        importSubMenu = Menu(file, tearoff=0)
        importSubMenu.add_command(label='from CSV...', command=self.openAsCSV)
        importSubMenu.add_command(label='from OTSoft CSV...',
                                  command=self.openAsOTSoftCSV)
        file.add_cascade(label='Import', menu=importSubMenu)

        exportSubMenu = Menu(file, tearoff=0)
        exportSubMenu.add_command(label='to CSV...', command=self.saveAsCSV)
        exportSubMenu.add_command(label='to OTSoft CSV...',
                                  command=self.saveAsOTSoftCSV)
#       exportSubMenu.add_command(label='to Praat...', command=self.onExport2Praat)
        file.add_cascade(label='Export', menu=exportSubMenu)
        file.add_separator()
        file.add_command(label='Quit',
                         command=self.quit, 
                         accelerator='%s+Q' % acc,
                         underline=0)
        self.menu.add_cascade(label='File', menu=file, underline = 0)

        grammar = Menu(parent, tearoff=0)
        self.grammar_menu = grammar
        grammar.add_command(label='Add constraint', 
                            command=self.onAddConstraint,
                            accelerator='%s+A' % acc)
        grammar.add_command(label='Demote constraint...', 
                            command=self.onDemoteConstraint,
                            accelerator='%s+D' % acc)
        if len(self.gmr.hierarchy) < 3:
            init=DISABLED
        else:
            init=NORMAL
        grammar.add_command(label='Remove last constraint', 
                            command=self.onRemoveLastConstraint,
                            accelerator='%s+R' % acc,
                            state=init)
        grammar.add_separator()
        grammar.add_command(label='Add candidate', 
                            command=self.onAddCandidate,
                            accelerator='%s+P' % acc)
        grammar.add_command(label='Demote candidate...', 
                            command=self.onDemoteCandidate,
                            accelerator='%s+M' % acc)
        if len(self.gmr.outputs) < 3:
            init=DISABLED
        else:
            init=NORMAL
        grammar.add_command(label='Remove last candidate', 
            command=self.onRemoveLastCandidate,
                            accelerator='%s+K' % acc,
                            state=init)
        grammar.add_separator()
        grammar.add_command(label='Clear fatal violations', 
                            command=self.clearFatal)
        grammar.add_command(label='Clear all', command=self.onClearAll)
        grammar.add_separator()
        grammar.add_command(label='Eval', command = self.onEval, 
            accelerator='%s+E' % acc)
        grammar.add_command(label='Recursive Constraint Demotion',
                            command=self.onRcd)
        grammar.add_command(label='Show Relative Tableau',
                            command=self.onComparative,)
        #TODO:Decide on a nice accelerator if necessary
                            #accelerator='%s+ ???' % acc)
        # FROZEN    
        #grammar.add_command(label='Factorial typology', 
                    #command = self.onFactorialTypology,
                    #accelerator='%s+F' % acc)
        self.menu.add_cascade(label='Grammar',
                              menu=grammar, 
                              underline = 0)

        latex = Menu(parent, tearoff=0)
        latex.add_command(label='Generate LaTeX code', 
                          command=self.onLaTeX,
                          accelerator='%s+L' % acc,
                          underline=0)
        latex.add_command(label='Copy LaTeX code',
                          command=self.onLaTeXPaste,
                          accelerator='%s+Shift+C' % acc,
                          underline=0)
        self.menu.add_cascade(label='LaTeX', menu=latex, underline = 0)
                
        help = Menu(parent, tearoff=0)
        help.add_command(label='Example', command=self.onSimpleExample)
#       help.add_command(label='Mapping example', 
        # command=self.onMappingExample)
        help.add_command(label='Documentation', 
                         command=self.onDocumentation,
                         accelerator='%s+%s' % (acc, helpAcc))
        self.menu.add_cascade(label='Help', menu=help, underline = 0)
        help.add_separator()
        help.add_command(label='About', command=self.onAbout)
        help.add_command(label='Release notes', 
                 command=self.onReleaseNotes)
        help.add_command(label='Acknowledgements', 
                 command=self.onAcknowledgements)
        help.add_separator()
        help.add_command(label='Licence', command=self.onLicence)
        help.add_separator()
        help.add_command(label='Check for updates', 
                 command=self.checkUpdate,
            accelerator='%s+U' % acc)
        if self.params['debug']=='true':
            help.add_command(label='Reload code (Debug)',
                             command=self.onReload,)

        self.master.bind('<%s-s>' % bAcc, self.onSave) 
        self.master.bind('<%s-n>' % bAcc, self.onNew) 
        self.master.bind('<%s-o>' % bAcc, self.onOpen) 
        self.master.bind('<%s-q>' % bAcc, self.onQuit) 
        
        self.master.bind('<%s-a>' % bAcc, self.onAddConstraint) 
        self.master.bind('<%s-d>' % bAcc, self.onDemoteConstraint) 
        self.master.bind('<%s-r>' % bAcc, self.onRemoveLastConstraint) 
        self.master.bind('<%s-p>' % bAcc, self.onAddCandidate) 
        self.master.bind('<%s-m>' % bAcc, self.onDemoteCandidate) 
        self.master.bind('<%s-k>'% bAcc, self.onRemoveLastCandidate) 
        
        self.master.bind('<%s-l>' % bAcc, self.onLaTeX)
        self.master.bind('<Shift-%s-C>' % bAcc, self.onLaTeXPaste)
        self.master.bind('<Shift-%s-c>' % bAcc, self.onLaTeXPaste)
        self.master.bind('<Shift-%s-R>' % bAcc, self.onReload)
        self.master.bind('<Shift-%s-r>' % bAcc, self.onReload)
        self.master.bind('<%s-e>' % bAcc, self.onEval) 
        # FROZEN
        #self.master.bind('<%s-f>' % bAcc, self.onFactorialTypology) 
        self.master.bind('<%s-%s>' % (bAcc, helpBAcc), self.onDocumentation) 
        self.master.bind('<%s-u>' % bAcc, self.checkUpdate) 

        
    def setButtons(self):
        self.optionFrame = Frame(self)
        self.optionFrame.pack(side=BOTTOM, anchor=W)
        
        EvalBtn = Button(self.optionFrame, text = 'Eval', 
            command = self.onEval, width=10)
        EvalBtn.grid(column=0, row=0, sticky=W)
        LatexBtn = Button(self.optionFrame, text = 'LaTeX code', 
                          command = self.onLaTeX, width=10)
        LatexBtn.grid(column=0, row=1, sticky=W)
        # FROZEN
        #self.typoBtn = Button(self.optionFrame, 
                      #text = 'Factorial typology',
                      #command = self.onFactorialTypology)
        #self.typoBtn.pack(side=LEFT)
        
        #self.rankingBtn = Checkbutton(self.optionFrame, 
                          #text = 'Include rankings',
                          #command = self.onIncludeRankings)
        #self.rankingBtn.pack(side=LEFT)
        #self.rankingBtn.select()
        

        self.shadeBtn = Checkbutton(self.optionFrame, 
                                    text = 'Shade cells', justify = LEFT,
                                    command=self.onUserShade)
        self.shadeBtn.grid(column=1, row=0, sticky=W)
        self.shadeBtn.select()
        
        self.tipaBtn = Checkbutton(self.optionFrame,
                                   text = 'Use Tipa',
                                   command=self.onTipa)
        self.tipaBtn.grid(column=1, row=1, sticky=W)
        self.tipaBtn.select()


        self.rotateBtn = Checkbutton(self.optionFrame,
                                     text = 'Rotate constraints',
                                     command = self.onRotate)
        self.rotateBtn.grid(column=2, row=0, sticky=W)

        self.ftnSizeBtn = Checkbutton(self.optionFrame,
                                      text = 'Footnote size',
                                      command = self.onFtnSize)
        self.ftnSizeBtn.grid(column=2, row=1, sticky=W)


        captionLbl = Label(self.optionFrame, text='Caption: ')
        captionLbl.grid(column=1, row=3, sticky=W)

        self.captionEntry = Entry(self.optionFrame)
        self.captionEntry.grid(column=2, row=3, sticky=W)

        labelLbl = Label(self.optionFrame, text='Label: ')
        labelLbl.grid(column=1, row=4, sticky=W)

        self.labelEntry = Entry(self.optionFrame)
        self.labelEntry.grid(column=2, row=4, sticky=W)
        self.labelEntry.insert(END, '%s:' % self.params['environment'][:3])

    def setTableauFrame(self):
        self.tableauFrame = Frame(self)
        self.tableauFrame.pack(side=TOP)


    def displayTableau(self):
        """Update the whole view from the model -- that is,
        display self.gmr's tableau.
        Also save the Entry cells for future access"""
        self.cells = []
        
        if sys.platform == 'darwin':
            relief = self.getMacCellRelief()
        else:
            relief = RIDGE
        latexTableau = self.gmr.getLaTeXTableau()
        for row in range(len(self.gmr.tableau)):
            cols = []
            
            for col in range(len(self.gmr.tableau[row])):
                e = Entry(self.tableauFrame, relief=relief)

                gridoptions = {'row':row, 'column':col, 'sticky':NSEW}
                if row==0: gridoptions['pady'] = 4
                if col==0: gridoptions['padx'] = 4
                e.grid(**gridoptions)

                if col != 0:
                    e.config(justify=CENTER)
                e.insert(END, latexTableau[row][col])
                
                cols.append(e)
                
            self.cells.append(cols)
    
    def redraw(self):
        self.tableauFrame.destroy()
        self.setTableauFrame()
        self.displayTableau()
        self.redrawMenus()
        if self.gmr.isEvaled: self.onEval() # refresh Eval
    def redrawMenus(self):
        if len(self.gmr.hierarchy) < 3:
            self.grammar_menu.entryconfig(2, state=DISABLED)
        else:
            self.grammar_menu.entryconfig(2, state=NORMAL)
        if len(self.gmr.outputs) < 3:
            self.grammar_menu.entryconfig(6, state=DISABLED)
        else:
            self.grammar_menu.entryconfig(6, state=NORMAL)
    
    ####################
    ## WINDOW BUTTONS ##
    ####################
    def onEval(self, e=None):
        self.update()
        self.gmr.eval()
        self.displayTableau()
        
        for row in range(1, len(self.gmr.tableau)):
            shade = False
            if row in self.gmr.winners:
                self.cells[row][0].delete(0, END)
                self.cells[row][0].insert('0', u'\u261e  ' + self.gmr.tableau[row][0])
                # this was the pre-1.2 code to highlight winners
                #self.cells[row][0].config(bg='#FFCF80')

            for col in range(len(self.gmr.tableau[row])):
                if shade and self.userShade:
                    self.cells[row][col].config(bg='gray')
                elif col-1 == self.gmr.fatalViolations[row-1]:
                    shade = True
                    
                    


    def onLaTeXPaste(self, e=None):
        self.update()
        try:
            text = self._export()
            self.clipboard_clear()
            self.clipboard_append(text)
        except OTFile.LaTeXExportError:
            showwarning('Too many candidates', 'You cannot export more than 26 candidates')
    def onLaTeX(self, e=None):
        self.update()
        
        comment = """
%% Don't forget these lines in your preamble:
%
% \\usepackage{pstricks}
% \\usepackage{colortab}
% \\usepackage{pifont}
"""
        if self.useTipa:
            comment += "% \\usepackage{tipa}\n"
        
        try:
            text = self._export() + comment
            self.update()
            win = Toplevel()
            win.title('LaTeX code')
            TextWindow(win, text)
        except OTFile.LaTeXExportError:
            showwarning('Too many candidates', 'You cannot export more than 26 candidates')
    def _export(self):
        kwargs = {'userShade': self.userShade,
                  'useTipa': self.useTipa,
                  'evaled': self.gmr.isEvaled,
                  'ftnSize':self.footnoteSize,
                  'doRotate':self.useRotate,
                  'label':self.labelEntry.get(),
                  'caption':self.captionEntry.get()}
        kwargs.update(self.params)
        return OTFile.toLaTeX(self.gmr, **kwargs)


            
    ###############
    ## FILE MENU ##
    ###############
    def onNew(self, e=None):
        newWindow()
    def onOpen(self, e=None):
        self.openAsOTableau()
    
    def onSave(self, e=None):
        self.update()
        self.gmr.clearFatalViolations()
        self.saveAsOTableau()
        self.redraw()
    def openAs(reader):
        def opener(self):
            "Create a new window if this one has been changed from the default"
            file = askopenfilename()
            if not file: return
            try:
                gmr = reader(file)
            except (OTFile.ZipFileError, IndexError, csv.Error), e:
                gmr = tryOtherReaders(reader, file)
                if gmr==None:
                    self.error("This file has an incorrect format.")
                    return
            if self.gmr.tableau == [['','',''],['',0,0],['',0,0]]:
                self.master.title(os.path.basename(file))
                self.gmr = gmr
                self.gmr.isEvaled = False
                self.gmr.clearFatalViolations()
                self.gmr.initialize()
                self.redraw()
            else:
                newWindow(GUIAbsoluteTableau, os.path.basename(file), gmr)
        return opener
    openAsOTableau = openAs(OTFile.readOTableauFile)
    openAsCSV = openAs(OTFile.readCSV)
    # TODO:Handle OTSoft tableaux better, mainly by creating multiple windows
    # for multiple internal tableaux (though this is a temporary solution).
    openAsOTSoftCSV = openAs(OTFile.readOTSoftCSV)
    def onComparative(self):
        self.onEval() # this should not be necessary since
        # Comparative's constructor calls self.gmr.eval too. However,
        # it doesn't work now without it, and it provides visual feedback
        # that the current grammar was evalled.
        newWindow(GUIComparativeTableau,
                  self.master.title(),
                  OTGrammar.Comparative(self.gmr.getAbsoluteTableau()))
    def saveAsOTableau(self):
        file = asksaveasfilename()
        if file:
            file = OTFile.addExtension(file, 'otableau')
            self.master.title(os.path.basename(file))
            OTFile.writeOTGrammar(file, self.gmr.getAbsoluteTableau())
        
    def saveAsCSV(self):
        file = asksaveasfilename()
        if file and not OTFile.writeCSV(OTFile.addExtension(file, 'csv'),
                                        self.gmr.getAbsoluteTableau()):
            self.error('The file could not be written.')
    def saveAsOTSoftCSV(self):
        file = asksaveasfilename()
        if file and not OTFile.writeOTSoftCSV(OTFile.addExtension(file, 'csv'),
                                              self.gmr.getAbsoluteTableau()):
            self.error('The file could not be written.')
    def onQuit(self, e=None):
        self.quit()
            
    ##################
    ## GRAMMAR MENU ##
    ##################
    def onAddConstraint(self, e=None):
        self.update()
        self.gmr.addConstraint()
        self.clearFatal()
        
    def onRemoveLastConstraint(self, e=None):
        self.update()
        con = len(self.gmr.hierarchy)
        if con <= 2:
            showwarning('Info', 'You need at least 2 constraints')
        else: 
            self.gmr.removeConstraint(con)
            self.redraw()
            
    def onDemoteConstraint(self, e=None):
        c = askinteger('Entry', 'Constraint number:')
        if c != None:
            self.update()
            self.gmr.demoteConstraint(c)
            self.redraw()
        
    def onAddCandidate(self, e=None):
        self.update()
        self.gmr.addCandidate()
        self.clearFatal()
        
    def onRemoveLastCandidate(self, e=None):
        cand = len(self.gmr.outputs)
        if cand <= 2:
            showwarning('Info', 'You need at least 2 candidates')
        else:
            self.update()
            self.gmr.removeCandidate(cand)
            self.redraw()
    def onDemoteCandidate(self, e=None):
        c = askinteger('Entry', 'Candidate number:')
        if c != None:
            self.update()
            self.gmr.demoteCandidate(c)
            self.redraw()
        
    def clearFatal(self):
        # self.update()
        self.gmr.clearFatalViolations()
        self.gmr.isEvaled = False
        self.redraw()
        
    def onClearAll(self, e=None):
        self.gmr.tableau = [['','',''],['',0,0],['',0,0]]
        self.gmr.isEvaled = False
        self.redraw()
        
    def onRcd(self):
        self.update()
        self.gmr.rearrange(concat(rcd.rcd(self.gmr.getComparativeTuples())))
        self.displayTableau()
    def onFactorialTypology(self, e=None):
        self.update()
        self.gmr.clearFatalViolations()
        self.gmr.factorialTypology()
        result = self.gmr.getFactorialTypologyAsString(self.includeRankings)
        win = Toplevel()
        win.title('Factorial typology')
        TextWindow(win, result)
        self.redraw()
        
    ###############
    ## HELP MENU ##
    ###############
    def onSimpleExample(self, e=None):
        self.gmr.clearFatalViolations()
        self.gmr.tableau = [['tat','Max','Dep','NoCoda'], ['tat', 0,0,1], ['ta',1,0,0],
            ['tati', 0,1,0]]
        self.gmr.isEvaled = False
        self.gmr.initialize()
        self.redraw()
 
#   def onMappingExample(self):
#       self.gmr.clearFatalViolations()
#       self.gmr.tableau = [['','Max','Dep','NoCoda'], ['tat -> tat', '','','*'], 
#           ['tat -> ta','*','',''], ['tat -> tati', '','*',''], 
#           ['at -> nat', '','*','*'], ['at -> na', '*','*','']]
#       self.gmr.isEvaled = False
#       self.redraw()
        
    def onDocumentation(self, e=None):
        doc = os.path.join(os.path.abspath('.'), 'OTableau-doc.pdf')
        warning = """Problem opening documentation.
Try changing your PDF viewer in the config file 
and check OTableau-doc.pdf is in OTableau's directory"""

        if sys.platform[:3] == 'win' and self.params['win_pdf_reader_found']:
            cmd = self.params['win_pdf_viewer']
        elif sys.platform == 'darwin': 
            cmd = 'open ' # FIXME: does this work on plain darwin?
            # Ugly fix for applets !
            # FIXME: how do we get the current dir from within an applet?
            uglyfix = '/Applications/OTableau.app/Contents/Resources/' + doc
            if os.path.isfile(uglyfix):
                doc = uglyfix
        else:
            cmd = self.params['unix_pdf_viewer']
            
        if ( self.params.has_key('win_pdf_reader_found') 
            and self.params['win_pdf_reader_found'] ) or sys.platform[:3] != 'win':
            r = os.system('%s %s' % (cmd, doc))

        if sys.platform[:3] != 'win' and r != 0:
            self.error(warning)
            
    def onAbout(self):
        text = """
OTableau version %s
(C) 2006 Julien Eychenne
See my website at http://www.exuna.net
OTableau is written in Python and is open-source.
""" % version #self.params['version']

        showinfo('About', text)
        
    def onAcknowledgements(self):
        acknow = OTabTextData.acknowledgements
        win = Toplevel()
        win.title('Acknowledgements')
        TextWindow(win, acknow)

    def onLicence(self):
        licence = OTabTextData.gpl
        win = Toplevel()
        win.title('Licence')
        TextWindow(win, licence)

    def onReleaseNotes(self):
        notes = OTabTextData.releaseNotes
        win = Toplevel()
        win.title('Release notes')
        TextWindow(win, notes)
        
        
    ###################
    ## OTHER METHODS ##
    ###################
    def checkUpdate(self, e=None):
        url='http://www.exuna.net/en/tools/otableau.php'
        pattern = re.compile(r'\[VERSION=(.*?)\]')
        page = []
        onlineVersion = '0.0'

        try:
            f = urllib.urlopen(url)

            for ln in f.readlines():
                if pattern.search(ln):
                    res = pattern.search(ln)
                    onlineVersion = res.group(1)
            f.close()

            _onlineVersion = map(int, onlineVersion.split('.'))
            if some(uncurry(gt), zip(_onlineVersion, versionNumber)):
                msg = 'Version %s is available online.' % onlineVersion
                showinfo('Update', '%s See\n %s' % (msg, url))
            else:
                showinfo('Update', 'No update available')
        except IOError:
            self.error('You do not seem to be connected to the Internet.')

    
    def update(self):
        """Copy the entire view into the model--ie, updates self.gmr
        I don't think that the individual cells have key handlers bound, but
        that would probably be a better way to lazily update the grammar.
        (if Tk key-handling is up to the task)"""
        self.gmr.update([[cell.get() for cell in row] for row in self.cells])
        return
        for row in range(len(self.gmr.tableau)):
            for col in range(len(self.gmr.tableau[row])):

                if row == 0 or col == 0:
                    label = self.cells[row][col].get().replace(u'\u261e  ', '')
                    self.gmr.tableau[row][col] = label
                else :
                    label = self.cells[row][col].get()
                    self.gmr.tableau[row][col] = star2num(label)

        self.gmr.initialize()
        # self.initialiseConList()
    def flipper(property):
        def flip(self):
            setattr(self, property, not getattr(self, property))
        return flip
    onTipa = flipper('useTipa')
    onFtnSize = flipper('footnoteSize')
    onUserShade = flipper('userShade')
    onIncludeRankings = flipper('includeRankings')
    onRotate = flipper('useRotate')
#   onEvaluateMappings = flipper('evaluateMappings')
    
    def error(self, message):
        showerror('Error', message)

    
    def debug(self, text):
        win = Toplevel()
        win.title('Debug window')
        TextWindow(win, text)
    def onReload(self, e=None):
        "This is a first attempt, so a lot is probably unnecessary"
        import Interface
        import OTabTextWindow
        import OTabSettings
        import __version__
        reload(OTGrammar); reload(OTFile)
        reload(OTabSettings); reload(OTabTextData); reload(__version__)
        reload(OTabTextWindow); reload(Interface);
        reload(rcd)
        from OTabTextWindow import TextWindow
        # TODO: Copy boolean settings to new window
        newWindow(Interface.GUIAbsoluteTableau, self.master.title(), self.gmr)

    def notImplemented(self):
        showinfo('Missing feature','This feature has not been implemented yet')


    def getMacCellRelief(self):
        if self.params['mac_cell_relief'] == 'None':
            return None
        else: return RIDGE
class GUIAbsoluteTableau(GUITableau):
    pass
class GUIComparativeTableau(GUITableau):
    """Overrides setButtons, setMenu, and _export.
    Its gmr is an instance of Comparative and overrides update."""
    def setButtons(self):
        self.optionFrame = Frame(self)
        self.optionFrame.pack(side=BOTTOM, anchor=W)
        
        RcdBtn = Button(self.optionFrame, text = 'RCD',
            command = self.onRcd, width=10)
        RcdBtn.grid(column=0, row=0, sticky=W)
        LatexBtn = Button(self.optionFrame, text = 'LaTeX code',
                          command = self.onLaTeX, width=10)
        LatexBtn.grid(column=0, row=1, sticky=W)

        self.tipaBtn = Checkbutton(self.optionFrame,
                                   text = 'Use Tipa',
                                   command=self.onTipa)
        self.tipaBtn.grid(column=1, row=1, sticky=W)
        self.tipaBtn.select()


        self.rotateBtn = Checkbutton(self.optionFrame,
                                     text = 'Rotate constraints',
                                     command = self.onRotate)
        self.rotateBtn.grid(column=2, row=0, sticky=W)

        self.ftnSizeBtn = Checkbutton(self.optionFrame,
                                      text = 'Footnote size',
                                      command = self.onFtnSize)
        self.ftnSizeBtn.grid(column=2, row=1, sticky=W)


        captionLbl = Label(self.optionFrame, text='Caption: ')
        captionLbl.grid(column=1, row=3, sticky=W)

        self.captionEntry = Entry(self.optionFrame)
        self.captionEntry.grid(column=2, row=3, sticky=W)

        labelLbl = Label(self.optionFrame, text='Label: ')
        labelLbl.grid(column=1, row=4, sticky=W)

        self.labelEntry = Entry(self.optionFrame)
        self.labelEntry.grid(column=2, row=4, sticky=W)
        self.labelEntry.insert(END, '%s:' % self.params['environment'][:3])
    def setMenu(self, parent):
        self.menu = Menu(parent)
        parent.config(menu=self.menu)
        
        file = Menu(parent, tearoff=0)
        file.add_command(label='New', command=self.onNew, 
            accelerator='%s+N' % acc, underline=0)
        file.add_command(label='Open...', command=self.onOpen, 
            accelerator='%s+O' % acc, underline=0)
        file.add_separator()
        file.add_command(label='Save as...', command=self.onSave, 
            accelerator='%s+S' % acc, underline=0)
        file.add_separator()
        importSubMenu = Menu(file, tearoff=0)
        importSubMenu.add_command(label='from CSV...', command=self.openAsCSV)
        importSubMenu.add_command(label='from OTSoft CSV...',
                                  command=self.openAsOTSoftCSV)
        file.add_cascade(label='Import', menu=importSubMenu)

        exportSubMenu = Menu(file, tearoff=0)
        exportSubMenu.add_command(label='to CSV...', command=self.saveAsCSV)
#       exportSubMenu.add_command(label='to Praat...', command=self.onExport2Praat)
        file.add_cascade(label='Export', menu=exportSubMenu)
        file.add_separator()
        file.add_command(label='Quit', command=self.quit, 
            accelerator='%s+Q' % acc, underline=0)
        self.menu.add_cascade(label='File', menu=file, underline = 0)

        grammar = Menu(parent, tearoff=0)
        self.grammar_menu = grammar
        grammar.add_command(label='Add constraint', 
            command=self.onAddConstraint, accelerator='%s+A' % acc)
        grammar.add_command(label='Demote constraint...', 
            command=self.onDemoteConstraint, accelerator='%s+D' 
                    % acc)
        grammar.add_command(label='Remove last constraint', 
            command=self.onRemoveLastConstraint, accelerator='%s+R' 
                    % acc)
        grammar.add_separator()
        grammar.add_command(label='Add candidate', 
            command=self.onAddCandidate, accelerator='%s+P' % acc)
        grammar.add_command(label='Demote candidate...', 
            command=self.onDemoteCandidate, accelerator='%s+M' 
                    % acc)
        grammar.add_command(label='Remove last candidate', 
            command=self.onRemoveLastCandidate, accelerator='%s+K' 
                    % acc)
        grammar.add_separator()
        grammar.add_command(label='Clear all', command=self.onClearAll)
        grammar.add_separator()
        grammar.add_command(label='Recursive Constraint Demotion',
                            command=self.onRcd)
        grammar.add_command(label='Show Absolute Tableau',
                            command=self.onAbsolute,)
        #TODO:Decide on a nice accelerator if necessary
                            #accelerator='%s+ ???' % acc)
        self.menu.add_cascade(label='Grammar', menu=grammar, 
                      underline = 0)


        latex = Menu(parent, tearoff=0)
        latex.add_command(label='Generate LaTeX code', 
                  command=self.onLaTeX,
                  accelerator='%s-L' % acc, underline=0)
        latex.add_command(label='Copy LaTeX code',
                  command=self.onLaTeXPaste,
                  accelerator='%s+Shift+C' % acc, underline=0)
        self.menu.add_cascade(label='LaTeX', menu=latex, underline = 0)
                
        help = Menu(parent, tearoff=0)
        help.add_command(label='Documentation', 
                 command=self.onDocumentation,
                 accelerator='%s+%s' % (acc, helpAcc))
        self.menu.add_cascade(label='Help', menu=help, underline = 0)
        help.add_separator()
        help.add_command(label='About', command=self.onAbout)
        help.add_command(label='Release notes', 
                 command=self.onReleaseNotes)
        help.add_command(label='Acknowledgements', 
                 command=self.onAcknowledgements)
        help.add_separator()
        help.add_command(label='Licence', command=self.onLicence)
        help.add_separator()
        help.add_command(label='Check for updates', 
                 command=self.checkUpdate,
            accelerator='%s+U' % acc)

        self.master.bind('<%s-s>' % bAcc, self.onSave) 
        self.master.bind('<%s-n>' % bAcc, self.onNew) 
        self.master.bind('<%s-o>' % bAcc, self.onOpen) 
        self.master.bind('<%s-q>' % bAcc, self.onQuit) 
        
        self.master.bind('<%s-a>' % bAcc, self.onAddConstraint) 
        self.master.bind('<%s-d>' % bAcc, self.onDemoteConstraint) 
        self.master.bind('<%s-r>' % bAcc, self.onRemoveLastConstraint) 
        self.master.bind('<%s-p>' % bAcc, self.onAddCandidate) 
        self.master.bind('<%s-m>' % bAcc, self.onDemoteCandidate) 
        self.master.bind('<%s-k>'% bAcc, self.onRemoveLastCandidate) 
        
        self.master.bind('<%s-l>' % bAcc, self.onLaTeX)
        self.master.bind('<Shift-%s-C>' % bAcc, self.onLaTeXPaste)
        self.master.bind('<Shift-%s-c>' % bAcc, self.onLaTeXPaste)

        self.master.bind('<%s-%s>' % (bAcc, helpBAcc), self.onDocumentation) 
        self.master.bind('<%s-u>' % bAcc, self.checkUpdate) 
    def onAbsolute(self):
        newWindow(GUIAbsoluteTableau,
                  self.master.title(),
                  OTGrammar.Tableau(self.gmr.getAbsoluteTableau().tableau))
    def _export(self):
        assert isinstance(self.gmr, OTGrammar.Comparative)
        # save-self.gmr.outputs and modify it temporarily
        original_outputs = self.gmr.outputs
        self.gmr.outputs = [o+" ~ "+self.gmr.winner for o in self.gmr.outputs]
        kwargs = {'userShade': False,
                  'useTipa': self.useTipa,
                  'evaled': self.gmr.isEvaled,
                  'ftnSize':self.footnoteSize,
                  'doRotate':self.useRotate,
                  'label':self.labelEntry.get(),
                  'caption':self.captionEntry.get()}
        kwargs.update(self.params)
        s = OTFile.toLaTeX(self.gmr, **kwargs)
        self.gmr.outputs = original_outputs # restore
        return s
    def redrawMenus(self):
        if len(self.gmr.hierarchy) < 3:
            self.grammar_menu.entryconfig(2, state=DISABLED)
        else:
            self.grammar_menu.entryconfig(2, state=NORMAL)
        if len(self.gmr.outputs) < 2:
            self.grammar_menu.entryconfig(6, state=DISABLED)
        else:
            self.grammar_menu.entryconfig(6, state=NORMAL)

def newWindow(GUI=GUIAbsoluteTableau, title='OTableau', tableau=None):
    win = Tk()
    win.title(title)
    g = GUI(win, tableau)
    g.pack()
def tryOtherReaders(reader, file):
    """ OTSoft in place of CSV: No error. This cannot (easily) be detected.
    CSV in place of OTSoft: IndexError
    OTableau in place of either: csv.Error
    either in place of OTableau: OTFile.ZipFileError"""
    for other in lst_except([OTFile.readOTableauFile,
                             OTFile.readOTSoftCSV,
                             OTFile.readCSV],
                            reader):
        try:
            return other(file)
        except (OTFile.ZipFileError, IndexError, csv.Error):
            continue
    return None
## This version works for all x, but is slower.
## def lst_except(l, *excludeds):
##     return [x for x in l if x not in excludeds]
## This version works for hashable x only, but should be faster.
## This kind of stinks
def lst_except(l, *excludeds):
    return list(set(l) - set(excludeds))

if __name__ == '__main__':

    m = [['tat','Max','NoCoda'], [u't\u261et', 0,1], ['ta',1,0]]
            
#   t = []
#   for a in range(30):
#       t.append(['','','',''])
            
    root = Tk()
    root.title('OTableau')

    GUIAbsoluteTableau(root, m).pack()
    
    root.mainloop()
